﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Security;
using Mozlite.Components;
using Mozlite.Components.Configuration;

namespace Mozlite
{
    /// <summary>
    /// 用户管理类。
    /// </summary>
    public static class Users
    {
        #region save
        /// <summary>
        /// 编辑用户档案。
        /// </summary>
        /// <param name="user">用户实例对象。</param>
        /// <returns>返回编辑状态。</returns>
        public static CreateUserStatus EditProfile(User user)
        {
            if(DisallowNames.Current.IsDeny(user.DisplayName))
                return CreateUserStatus.DisplayNameIsDeny;
            if(!IsDisplayNameAvailable(user.UserID, user.DisplayName))
                return CreateUserStatus.DuplicateDisplayName;
            if(Database.ExecuteNonQuery(@"UPDATE moz_Users SET 
                Email = @Email, 
                DisplayName = @DisplayName, 
                LastUpdateDate = GETDATE(), 
                Extensions = @Extensions 
                WHERE UserID = @UserID;", new
            {
                Email = user.Email,
                DisplayName = user.DisplayName,
                UserID = user.UserID,
                Extensions = Cores.Serialize(user)
            }))
            {
                RefreshUser(user.UserName);
                return CreateUserStatus.Updated;
            }
            return CreateUserStatus.UnkonwnError;
        }
        /// <summary>
        /// 修改当前用户的密码。
        /// </summary>
        /// <param name="password">新密码。</param>
        /// <returns>返回修改结果。</returns>
        public static bool ChangePassword(string password)
        {
            var user = AppContext.Current.User;
            password = EncryptPassword(password);
            if(Database.ExecuteNonQuery(@"UPDATE moz_Users SET 
                Password = @Password 
                WHERE UserID = @UserID;", new
            {
                Password = password,
                UserID = user.UserID
            })) {
                AppContext.Current.User.Password = password;
                return true;
            }
            return false;
        }

        /// <summary>
        /// 保存用户。
        /// </summary>
        /// <param name="user">用户实例对象。</param>
        /// <param name="ignoreDenyName">忽然禁用名称。</param>
        /// <returns>返回保存状态枚举。</returns>
        public static CreateUserStatus Save(this User user, bool ignoreDenyName)
        {
            DisallowNames names = DisallowNames.Current;
            //用户名
            if(!ignoreDenyName && names.IsDeny(user.UserName))
                return CreateUserStatus.DisplayNameIsDeny;
            if(!IsNameAvailable(user.UserID, user.UserName))
                return CreateUserStatus.DuplicateUserName;
            if(!ignoreDenyName && names.IsDeny(user.DisplayName))
                return CreateUserStatus.DisplayNameIsDeny;
            if (!ignoreDenyName && !IsDisplayNameAvailable(user.UserID, user.DisplayName))
                return CreateUserStatus.DuplicateDisplayName;
            //密码
            if (user.Password.HasValue())
                user.Password = EncryptPassword(user.Password);
            if (user.UserID > 0)
            {
                if (Database.ExecuteNonQuery("UPDATE moz_Users SET UserName = @UserName, DisplayName = @DisplayName{0}, Email = @Email, AccountStatus = @AccountStatus, RoleLevel = @RoleLevel, Extensions = @Extensions WHERE UserID = {1};".FormatWith(user.Password.HasValue() ? ", [Password] = @Password" : null, user.UserID), new {
                    UserName = user.UserName,
                    DisplayName = user.DisplayName,
                    Password = user.Password,
                    Email = user.Email,
                    AccountStatus = user.AccountStatus,
                    RoleLevel = user.RoleLevel,
                    Extensions = Cores.Serialize(user)
                }))
                    return CreateUserStatus.Updated;
            }
            else if (Database.ExecuteNonQuery("INSERT INTO moz_Users(UserName, DisplayName, [Password], Email, CreateDate, RegisterIP, PublicKey, AccountStatus, RoleLevel, Extensions)VALUES(@UserName, @DisplayName, @Password, @Email, GETDATE(), @RegisterIP, @PublicKey, @AccountStatus, @RoleLevel, @Extensions);",
                new {
                    UserName = user.UserName,
                    DisplayName = user.DisplayName,
                    Password = user.Password,
                    Email = user.Email,
                    RegisterIP = user.RegisterIP,
                    PublicKey = user.PublicKey,
                    AccountStatus = AccountStatus.Approved,
                    RoleLevel = user.RoleLevel,
                    Extensions = Cores.Serialize(user)
                }))
                return CreateUserStatus.Created;
            return CreateUserStatus.UnkonwnError;
        }

        /// <summary>
        /// 保存用户。
        /// </summary>
        /// <typeparam name="T">用户类型。</typeparam>
        /// <param name="user">用户实例对象。</param>
        /// <returns>返回保存状态枚举。</returns>
        public static CreateUserStatus Save<T>(this T user) where T : User, new()
        {
            return user.Save(false);
        }
        #endregion

        #region annonymous
        /// <summary>
        /// 匿名。
        /// </summary>
        public const string Anonymous = "Anonymous";

        /// <summary>
        /// 获取你们用户的唯一GUID。
        /// </summary>
        /// <returns>返回匿名用户的唯一GUID。</returns>
        public static Guid GetAnonymousID()
        {
            try
            {
                return new Guid(AppContext.Current.Context.Request.AnonymousID);
            }
            catch { }
            var cookie = Cookies.GetCookie(Users.Anonymous);
            if(cookie == null)
            {
                cookie = new HttpCookie(Users.Anonymous, Guid.NewGuid().ToString());
            }
            cookie.Expires = DateTime.Now.AddMinutes(20);
            Cookies.SetCookie(cookie);
            return cookie.Value.ToGuid();
        }

        /// <summary>
        /// 移除你们唯一GUID。
        /// </summary>
        public static void RemoveAnonymousID()
        {
            Cookies.DeleteCookie(Users.Anonymous);
        }
        #endregion

        #region valid
        /// <summary>
        /// 加密密码字符串。
        /// </summary>
        /// <param name="password">密码明文。</param>
        /// <returns>返回加密后的字符串。</returns>
        public static string EncryptPassword(string password)
        {
            if (password != null)
                password = password.Trim();
            return Cores.Encrypt(Cryptography.EncryptionAlgorithm.MD5, Cores.Encrypt(Cryptography.EncryptionAlgorithm.SHA1, password + "MOZCMSISCONTENTMANAGERSYSTEM"));
        }

        /// <summary>
        /// 判断当前用户的密码是否为<paramref name="password"/>。
        /// </summary>
        /// <param name="password">用户判断的密码。</param>
        /// <returns>返回判断结果。</returns>
        public static bool IsValid(string password) {
            return AppContext.Current.User.Password.IsCaseInsensitiveEqual(EncryptPassword(password));
        }

        /// <summary>
        /// 验证用户。
        /// </summary>
        /// <param name="userName">用户名称。</param>
        /// <param name="password">用户密码。</param>
        /// <returns>返回用户登录状态。</returns>
        public static LoginUserStatus ValidUser(string userName, string password)
        {
            User user = GetUser(userName);
            if (user == null || user.IsAnonymous || !user.Password.IsCaseInsensitiveEqual(EncryptPassword(password)))
                return LoginUserStatus.InvalidCredentials;

            if (user.AccountStatus == AccountStatus.Pending)
            {
                return LoginUserStatus.AccountPending;
            }
            if (user.AccountStatus == AccountStatus.Disapproved)
            {
                return LoginUserStatus.AccountDisapproved;
            }
            if (user.AccountStatus == AccountStatus.Banned)
                return LoginUserStatus.AccountBanned;
            AppContext.Current.User = user;
            return LoginUserStatus.Success;
        }

        /// <summary>
        /// 写入登录Cookie，然后转向用户中心。
        /// </summary>
        /// <param name="autoLogin">是否保存登录。</param>
        public static void SignIn(bool autoLogin)
        {
            HttpContext context = HttpContext.Current;
            if(context != null)
            {
                HttpCookie authCookie = FormsAuthentication.GetAuthCookie(AppContext.Current.UserName, autoLogin);
                if(autoLogin)
                    authCookie.Expires = DateTime.Now.AddMonths(1);
                Cookies.SetCookie(authCookie);

                //移除匿名信息
                LoginOnline();
            }
        }

        /// <summary>
        /// 设置登录成功后更新的数据库内容。
        /// </summary>
        public static void SetLoginStatus()
        {
            var user = AppContext.Current.User;
            user.LoginIP = HttpContext.Current.Request.UserHostAddress;
            user.LastLoginDate = DateTime.Now;
            Database.ExecuteNonQuery("UPDATE moz_Users SET LastLoginDate = GETDATE(), LoginIP = @LoginIP WHERE UserID = @UserID;", new { LoginIP = user.LoginIP, UserID = user.UserID });
        }
        /// <summary>
        /// 退出登录。
        /// </summary>
        public static void Logout() {
            FormsAuthentication.SignOut();
        }
        #endregion

        #region get
        private static string GetCacheKey(string userName)
        {
            return "UsersByName::{0}".FormatWith(userName);
        }

        private static void RefreshUser(string userName)
        {
            string key = GetCacheKey(userName);
            Caches.Remove(key);
        }

        /// <summary>
        /// 获取用户。
        /// </summary>
        /// <param name="userID">用户ID。</param>
        /// <returns>返回用户实例对象。</returns>
        public static User GetUser(int userID)
        {
            return GetUser<User>(userID);
        }

        /// <summary>
        /// 获取用户。
        /// </summary>
        /// <typeparam name="T">用户类型。</typeparam>
        /// <param name="userID">用户ID。</param>
        /// <returns>返回用户实例对象。</returns>
        public static T GetUser<T>(int userID) where T : User, new()
        {
            return Database.ExecuteReader<T>("SELECT * FROM moz_Users WHERE UserID = @UserID", new { UserID = userID }, "Extensions");
        }

        /// <summary>
        /// 获取用户。
        /// </summary>
        /// <typeparam name="T">用户类型。</typeparam>
        /// <param name="userID">用户ID。</param>
        /// <param name="userName">用户名称。</param>
        /// <param name="fromCache">从缓存中获取用户。</param>
        /// <param name="isCacheable">是否缓存用户。</param>
        /// <returns>返回用户实例。</returns>
        public static T GetUser<T>(string userName, bool fromCache = true, bool isCacheable = true) where T : User, new()
        {
            if (string.IsNullOrEmpty(userName))
                return null;
            T user = null;
            if (fromCache)
            {
                var cacheKey = GetCacheKey(userName);
                user = Caches.Get(cacheKey) as T;
                if (user == null)
                {
                    user = Database.ExecuteReader<T>("SELECT * FROM moz_Users WHERE UserName = @UserName", new { UserName = userName }, "Extensions");
                }
                if (isCacheable)
                {
                    Caches.Insert(cacheKey, user);
                }
            }
            return user;
        }

        /// <summary>
        /// 获取用户。
        /// </summary>
        /// <param name="userID">用户ID。</param>
        /// <param name="userName">用户名称。</param>
        /// <param name="fromCache">从缓存中获取用户。</param>
        /// <param name="isCacheable">是否缓存用户。</param>
        /// <returns>返回用户实例。</returns>
        public static User GetUser(string userName, bool fromCache = true, bool isCacheable = true)
        {
            return GetUser<User>(userName, fromCache, isCacheable);
        }
        #endregion

        #region helpers
        /// <summary>
        /// 判断用户名是否已经存在。
        /// </summary>
        /// <param name="userID">用户ID。</param>
        /// <param name="userName">用户名称。</param>
        /// <returns>返回判断结果。</returns>
        public static bool IsNameAvailable(int userID, string userName)
        {
            return !Database.ExecuteScalar<bool>(string.Format("SELECT 1 FROM moz_Users WHERE UserName = @UserName{0};", (userID > 0 ? (" AND UserID <> " + userID) : "")), new { UserName = userName }).To<bool>();
        }
        /// <summary>
        /// 判断用户名是否已经存在。
        /// </summary>
        /// <param name="userID">用户ID。</param>
        /// <param name="displayName">用户名称。</param>
        /// <returns>返回判断结果。</returns>
        public static bool IsDisplayNameAvailable(int userID, string displayName)
        {
            return !Database.ExecuteScalar<bool>(string.Format("SELECT 1 FROM moz_Users WHERE (DisplayName = @DisplayName OR UserName = @DisplayName){0};", (userID > 0 ? (" AND UserID <> " + userID) : "")), new { DisplayName = displayName }).To<bool>();
        }
        #endregion

        #region delete
        /// <summary>
        /// 删除用户。
        /// </summary>
        /// <param name="ids">用户ID集合。</param>
        /// <returns>返回删除结果。</returns>
        public static bool DeleteUsers(string ids) {
            if (ids == null)
                return false;

            ids = ids.FormatIDs();

            if (Database.ExecuteNonQuery("DELETE FROM moz_Users WHERE UserID{0} AND RoleLevel < @RoleLevel;".FormatWith(ids), new { RoleLevel = AppContext.Current.User.RoleLevel }))
            {
                Caches.RemoveByPattern("UsersByName::.*");
                return true;
            }
            return false;
        }
        #endregion

        #region online users
        private static readonly Dictionary<Guid, OnlineUser> onlineUsers = new Dictionary<Guid, OnlineUser>();
        private static readonly object onlineLocker = new object();

        private static void RemoveOnline(Guid guid) {
            lock(onlineLocker)
            {
                onlineUsers.Remove(guid);
            }
        }

        private static void LoginOnline() {
            if(StatisticsSettings.Current.Enabled)
            {
                RemoveOnline(GetAnonymousID());
                AddOnline();
                RemoveAnonymousID();
            }
        }

        /// <summary>
        /// 添加在线用户，登录时使用。
        /// </summary>
        private static void AddOnline()
        {
            var user = GetFromCurrentUser();
            if(user == null)
                return;
            lock(onlineLocker)
            {
                if(onlineUsers.ContainsKey(user.PublicKey))
                    onlineUsers[user.PublicKey] = user;
                else
                    onlineUsers.Add(user.PublicKey, user);
            }
        }

        /// <summary>
        /// 获取当前在线用户实例。
        /// </summary>
        /// <returns>返回在线用户实例。</returns>
        public static OnlineUser GetOnline()
        {
            var user = AppContext.Current.User;
            if(user.IsAnonymous)
                return null;
            OnlineUser onlineUser;
            lock(onlineLocker)
            {
                onlineUsers.TryGetValue(user.PublicKey, out onlineUser);
            }
            return onlineUser;
        }

        private static OnlineUser GetFromCurrentUser()
        {
            var user = AppContext.Current.User;
            if(!StatisticsSettings.Current.Enabled && user.IsAnonymous)
                return null;
            var onlineUser = new OnlineUser { UserID = user.UserID, UserName = user.UserName, PublicKey = user.PublicKey, LastAcitveDate = DateTime.Now };
            HttpContext context = HttpContext.Current;
            if(context != null)
            {
                onlineUser.IPAddress = context.Request.UserHostAddress;
                onlineUser.Url = context.Request.Url;
                onlineUser.UserAgent = context.Request.UserAgent;
                onlineUser.ReferrerUrl = context.Request.UrlReferrer;
            }
            return onlineUser;
        }

        /// <summary>
        /// 确认唯一登录。
        /// </summary>
        public static void CheckedUniqueeLogin()
        {
            var context = HttpContext.Current;
            var user = AppContext.Current.User;
            if(user.IsAnonymous || context == null)
                return;
            OnlineUser onlineUser;
            lock(onlineLocker)
            {
                onlineUsers.TryGetValue(user.PublicKey, out onlineUser);
            }
            if(onlineUser == null || onlineUser.IPAddress.IsCaseInsensitiveEqual(context.Request.UserHostAddress))
                AddOnline();//刷新在线实例
            else
            {
                FormsAuthentication.SignOut();
                HttpContext.Current.Response.Redirect("/UniqueeLogin", true);
            }

        }

        /// <summary>
        /// 计算在线用户。
        /// </summary>
        /// <returns>返回在线用户实例。</returns>
        public static List<OnlineUser> CaculateOnlines()
        {
            Dictionary<Guid, OnlineUser> offlines = new Dictionary<Guid, OnlineUser>();
            Dictionary<Guid, OnlineUser> onlines = new Dictionary<Guid, OnlineUser>();
            lock(onlineLocker)
            {
                foreach(var user in onlineUsers.Values)
                {
                    if(user.LastAcitveDate < DateTime.Now.AddMinutes(-20))
                        offlines.Add(user.PublicKey, user);
                    else
                        onlines.Add(user.PublicKey, user);
                }
                foreach(var key in offlines.Keys)
                {
                    onlineUsers.Remove(key);
                }
            }
            return onlines.Values.ToList();
            //todo:savetodb
        }
        #endregion
    }
}
